// Copyright (c) 2010 SuccessFactors, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the SuccessFactors, Inc. nor the names of
//       its contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.jxt;

/**
 * ExprParser -- a simple "${...}" expression extractor.  This class
 * is meant to be sub-classed.  The <code>text</code> and
 * <code>expr</code> methods will be called by <code>parse</code> to
 * indicate text and expression parts of the input.  This class
 * performs all the JSP EL escapes regarding backslashes.
 *
 * @author Jeffrey Ichnowski
 * @version $Revision: 8 $
 */
public abstract class ExprParser {
    private static final int START = 0;
    private static final int BACKSLASH = 1;
    private static final int DOLLAR = 2;
    private static final int EXPR = 3;

    private boolean _ignoreLoneDollar;

    public final void setIgnoreLoneDollar(boolean b) {
        _ignoreLoneDollar = b;
    }

    public final void parse(CharSequence source) {
        int n = source.length();
        StringBuilder text = new StringBuilder();
        StringBuilder expr = null;
        int bsCount = 0;
        int state = START;
        int nestingLevel = 0;
        boolean warnedLoneDollar = _ignoreLoneDollar;

        for (int i=0 ; i<n ; ++i) {
            char ch = source.charAt(i);

        stateLoop:
            for (;;) {
                switch (state) {
                case START:
                    if (ch == '\\') {
                        state = BACKSLASH;
                        bsCount = 1;
                    } else if (ch == '$') {
                        state = DOLLAR;
                    } else {
                        text.append(ch);
                    }
                    break stateLoop;

                case BACKSLASH:
                    if (ch == '\\') {
                        bsCount++;
                    } else if (ch == '$') {
                        // add half of the '\\' to the output text.
                        text.append(source, i - (bsCount>>1), i);
                        if ((bsCount & 1) == 1) {
                            // odd number of '\\', the '$' is escaped to text
                            text.append('$');
                            state = START;
                        } else {
                            state = DOLLAR;
                        }
                    } else {
                        // add all of the '\\' to the output text (and
                        // the current character)
                        text.append(source, i - bsCount, i+1);
                        state = START;
                    }
                    break stateLoop;

                case DOLLAR:
                    if (ch == '{') {
                        expr = new StringBuilder();
                        nestingLevel++;
                        state = EXPR;
                        break stateLoop;
                    } else {
                        if (!warnedLoneDollar) {
                            warn("expression.lone-dollar");
                            warnedLoneDollar = true;
                        }
                        // not an expression start, append a '$'
                        // (since it wasn't already), then reprocess
                        // the character in the START state.
                        text.append('$');
                        state = START;
                        continue stateLoop;
                    }

                case EXPR:
                    switch (ch) {
                    case '{':
                        nestingLevel++;
                        break;

                    case '}':
                        if (--nestingLevel > 0) {
                            break;
                        }

                        if (text.length() > 0) {
                            text(text.toString());
                            text.setLength(0);
                        }
                        expr(expr.toString());
                        expr = null;
                        state = START;
                        break stateLoop;
                    default:
                        break;
                    }

                    expr.append(ch);
                    break stateLoop;

                default:
                    throw new IllegalStateException();
                }
            }
        }

        switch (state) {
        case DOLLAR:
            text.append('$');
            break;
        case BACKSLASH:
            text.append(source, n - bsCount, n);
            break;
        case EXPR:
            warn("expression.unterminated");
            text.append("${").append(expr);
            break;
        default:
            break;
        }

        if (text.length() > 0) {
            text(text.toString());
        }
    }

    /** Callback for text literals. */
    protected abstract void text(String text);

    /** Callback for expression code. */
    protected abstract void expr(String expr);

    /** Callback for warnings. */
    protected void warn(String msg, Object ... args) {
    }

} // ExprParser
